<template>
  <div></div>
</template>

<script>
  
import md from '@/markdowns/jsBase-object.md'

export default {
  name: 'jsBase-object',
  components: {
    
  },
  data () {
    return {
      
    }
  },
  watch: {
    
  },
  computed: {
    
  },
  methods: {
    
  },
  created () {
    
  },
  mounted () {
    this.$el.innerHTML = this.$md(md)
    
    class Test {
      constructor () { this.a = 1 }
      do () { console.log(this.a) }
      set (a) { this.a = a }
    }
    
    let a = {
      int: 1,
      str: 'ssss',
      obj: {
        oArr: [1, 2, 3, 4],
        oo: {
          a: 1, b: 2
        },
        reg: new RegExp(/abcde.*?/, 'g'),
        test: new Test()
      },
      arr: [1, 2, 3, 4],
      map: new Map(),
      set: new Set(),
      err1: new Error(),
      err2: new ReferenceError(),
      date: new Date()
    }
    a.obj.cir = a
    a.obj.test.cir = a.map
    a.map.set(1, 'a')
    a.map.set('map1', 'b')
    a.map.set('cirmap', a.arr)
    a.set.add(1)
    a.set.add('set1')
    a.set.add(a.obj)
    this.origin = a
    console.log('构造原始对象', a)
    
    const b = deepClone(a)
    this.clone = b
    console.log('拷贝后的对象', b)
  },
}

// 检查对象类型
const getType = (target) => {
  return Object.prototype.toString.call(target).slice(8, -1);
}
// 需要向深处遍历的类型
const deepSearchList = ['Object', 'Array', 'Map', 'Set']
// 深拷贝主体
const deepClone = (obj, memCache = new WeakMap()) => {
  const objType = getType(obj)
  // 获取待原始对象的构造函数
  const InitFunc = obj.constructor
  // 克隆对象
  let cloneObj
  // 是否需要向深处遍历的标志
  const isDeepSearch = deepSearchList.indexOf(objType) >= 0 ? true : false 
  
  if (isDeepSearch) {
    // 检查是否需要向深处遍历，深处遍历可能出现循环结构，要在这里处理
    
    // 检查是否已经在内存缓存中找到过该对象
    const cache = memCache.get(obj)
    if (cache) {
      // console.log(cache)
      // 循环引用则返回地址
      return cache
    }
  }
  
  // 许多JS自带类，如果不用构造函数创建，而是用Object.create复制的话，许多功能无法正常发挥
  // 所以用switch进行了细分
  let len
  switch (objType) {
    // 以下都是涉及循环问题，需要深度遍历的对象
    case 'Array':
      cloneObj = []
      // deepSearchList指定的数据类型，有可能出现循环结构，递归前就要加入缓存
      memCache.set(obj, cloneObj)
      len = obj.length
      for (let i = 0; i < len; i++) {
        cloneObj.push(deepClone(obj[i], memCache))
      }
      break
    case 'Map':
      cloneObj = new InitFunc()
      memCache.set(obj, cloneObj)
      obj.forEach((value, key) => {
        cloneObj.set(key, deepClone(value, memCache))
      })
      break
    case 'Set':
      cloneObj = new InitFunc()
      memCache.set(obj, cloneObj)
      obj.forEach((value) => {
        cloneObj.add(deepClone(value, memCache))
      })
      break
    case 'Object':
      // Object除了系统的Object类型，还包括自定义类型
      // 新对象也必须要继承原型链
      cloneObj = Object.create(obj.constructor.prototype)
      memCache.set(obj, cloneObj)
      // 用Reflect.ownKeys相比起Object.keys，能够支持Symbol的键值
      const keys = Reflect.ownKeys(obj)
      len = keys.length
      for (let i = 0; i < len; i++) {
        const key = keys[i]
        cloneObj[key] = deepClone(obj[key], memCache)
      }
      break
      
    // 以下都是不涉及循环问题的对象
    case 'RegExp':
      cloneObj = new InitFunc(obj.source, obj.flags)
      break
    case 'Date':
    case (objType.match(/Error/) || {}).input:
      // 这些对象的对象体可以直接传入构造函数构造新对象
      cloneObj = new InitFunc(obj)
      break
    /*
    case 'Function':
      // 从使用角度来讲，引用函数体完全没问题，浅拷贝够用
      // 但是硬要深拷贝的话需要区分普通函数和箭头函数，箭头函数没有prototype，只能用eval拷贝函数内容
      // 具体参考其他人的文章吧
      break
    */
    default:
      // 基本数据类型，以及上面未提供特殊支持的数据类型，直接赋值
      cloneObj = obj
  }
  
  return cloneObj
}

</script>

<style>
</style>
